library(Seurat)
library(SnapATAC)
Loading required package: Matrix
Loading required package: rhdf5
library(tidyverse)
── Attaching packages ───────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.2.1     ✔ purrr   0.3.3
✔ tibble  2.1.3     ✔ dplyr   0.8.3
✔ tidyr   1.0.0     ✔ stringr 1.4.0
✔ readr   1.3.1     ✔ forcats 0.4.0
── Conflicts ──────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ tidyr::expand() masks Matrix::expand()
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
✖ tidyr::pack()   masks Matrix::pack()
✖ tidyr::unpack() masks Matrix::unpack()
library(DescTools)  # 4 AUC function
Registered S3 method overwritten by 'DescTools':
  method         from 
  reorder.factor gdata
library(glue)

Attaching package: ‘glue’

The following object is masked from ‘package:dplyr’:

    collapse
library(ggalluvial)  # 4 river plot
library(ggpubr)
Loading required package: magrittr

Attaching package: ‘magrittr’

The following object is masked from ‘package:purrr’:

    set_names

The following object is masked from ‘package:tidyr’:

    extract
source("~/multiOmic_benchmark/utils.R")
gg_color_hue <- function(n) {
  hues = seq(15, 375, length = n + 1)
  hcl(h = hues, l = 65, c = 100)[1:n]
}
## Make output directory
outdir <- "~/multiOmic_benchmark/report/output/20191113_labelTransferEDA_F74_v2/"
ifelse(!dir.exists(outdir), dir.create(outdir), FALSE)
[1] FALSE
# model.cca <- readRDS("~/models/modelCCA_union_hvg_F74_SCElist_20191113.RDS")
# model.liger <- readRDS("~/models/modelLiger_union_hvg_F74_SCElist_20191113.RDS")
# model.conos <- readRDS("~/models/modelConos_union_hvg_F74_SCElist_20191113.RDS")
seu.cca <- readRDS("~/models/labelTransferCCA_union_hvg_F74_SCElist_20191119.RDS")
seu.liger <- readRDS("~/models/labelTransferLiger_union_hvg_F74_SCElist_20191119.RDS")
seu.conos <- readRDS("~/models/labelTransferConos_union_hvg_F74_SCElist_20191119.RDS")
integrate_features <- scan("~/intFeatures_union_hvg_2000_F74_SCElist_20191113.txt", what='')
Read 10603 items
int.list <- list(CCA=seu.cca, Liger=seu.liger, Conos=seu.conos)
# ## Make method color palette
# method.palette <- brewer_palette_4_values(names(int.list), "Set1")

Embeddings

Visualize label transfer on original ATAC data (embedded SnapATAC bins)

Filter low confidence calls

ggpubr::ggarrange(
  plotlist = list(
    DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_CCA", cols=cell.type.pal, repel=TRUE) + 
      scale_color_manual(values = cell.type.pal, na.value="grey80") +
      ggtitle("CCA"),
    DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Liger", cols=cell.type.pal, repel=TRUE) + 
      scale_color_manual(values = cell.type.pal, na.value="grey80") + ggtitle("Liger"),
    DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Conos", cols=cell.type.pal, repel=TRUE) + 
      scale_color_manual(values = cell.type.pal, na.value="grey80") + ggtitle("Conos")
  ),
  common.legend = TRUE, ncol=3, nrow=1
) +
  ggsave(paste0(outdir, "umap_labels_filtered.png"), width=16, height = 8)
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

pl <-     DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_CCA", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("CCA")
plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
  If you'd like to see this geom implemented,
  Please open an issue with your example code at
  https://github.com/ropensci/plotly/issues
pl <-     DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Conos", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("Conos")
plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
  If you'd like to see this geom implemented,
  Please open an issue with your example code at
  https://github.com/ropensci/plotly/issues
orig.RNA.seu <- as.Seurat(orig.RNA)
orig.RNA.seu <- FindVariableFeatures(orig.RNA.seu)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
orig.RNA.seu <- ScaleData(orig.RNA.seu)
Centering and scaling data matrix

  |                                                                                                             
  |                                                                                                       |   0%
  |                                                                                                             
  |====================================================                                                   |  50%
  |                                                                                                             
  |=======================================================================================================| 100%
orig.RNA.seu <- RunPCA(orig.RNA.seu)
PC_ 1 
Positive:  TRBC2, TRBC1, HMGA1, HIST1H1C, HIST1H3H, ITM2A, HIST1H2BJ, SMIM24, TRAV13-2, FXYD2 
       TRBV7-2, PCGF5, HIST1H2BH, IL32, HIST1H2BN, CHAC1, RASD1, TRBV27, TRAV13-1, TRAV8-2 
       TRAV8-4, PTPN6, SELL, HIST1H2BG, TAGAP, TRDC, TRAV38-2DV8, TRAV29DV5, TRBV9, TRAV41 
Negative:  CALD1, COL5A2, COL6A1, COL6A2, SPARC, THY1, DCN, COL3A1, NFIB, SPARCL1 
       TSHZ2, CPE, PLAC9, NID1, FKBP10, PTN, FLRT2, MAP1B, EFEMP2, BGN 
       CXCL12, RBP1, LAMB1, AHNAK, COL1A1, COL5A1, FSTL1, LUM, LAMA4, MDK 
PC_ 2 
Positive:  SFRP1, NTRK2, PLAT, ISLR, NRK, SCARA5, ASPN, OSR1, OLFML3, MXRA8 
       CAPN6, PTPRD, PLP1, TMEFF2, CREB3L1, DKK3, CERCAM, MMP2, EBF2, SMOC2 
       CDO1, COL12A1, PDGFRA, LRRC17, THBS2, HTRA3, SFRP2, ANGPTL1, MAB21L1, MXRA5 
Negative:  MKI67, CDK1, NUSAP1, TOP2A, CCNA2, RRM2, UBE2C, BIRC5, KIFC1, TYMS 
       UBE2T, AURKB, CENPF, CENPM, CDCA8, TACC3, NCAPG, TPX2, ASF1B, CDKN3 
       GTSE1, CDCA3, HJURP, SPC25, MAD2L1, CDC20, PLK1, DLGAP5, NUF2, KIF22 
PC_ 3 
Positive:  CCNA2, NUF2, GTSE1, CDCA8, UBE2T, AURKB, PBK, CDK1, NCAPG, NDC80 
       KIFC1, CDCA3, HJURP, MAD2L1, SPC25, PLK1, KIF15, DEPDC1B, BIRC5, CDCA5 
       CKS1B, RRM2, DLGAP5, HMMR, CENPA, KIF22, KIF20A, CDCA2, CENPF, KIF2C 
Negative:  HLA-DRB5, HLA-DRA, TYROBP, HLA-DRB1, HLA-DPA1, HLA-DPB1, HLA-DQA1, C1QC, C1QB, RNASE1 
       HLA-DQB1, A2M, C1QA, CSF1R, STAB1, HCK, HLA-DMB, FOLR2, SAMHD1, LYZ 
       MS4A7, SPI1, CD36, MS4A6A, CYBB, TMEM176B, CD74, IGSF6, MPEG1, MS4A4A 
PC_ 4 
Positive:  GYPA, GYPB, NFE2, GYPE, ANK1, SLC4A1, RHAG, AHSP, DMTN, KLF1 
       GATA1, TMOD1, TMEM56, HBZ, HBG1, GMPR, C17orf99, SMIM5, HBQ1, TSPO2 
       ALAS2, PHOSPHO1, CR1L, TRIM58, HBM, EPB42, RHD, RHCE, SPTA1, SMIM1 
Negative:  TMSB10, TRBC2, CCL21, IL32, CXCL13, TRBC1, APLNR, CCL19, MIF, HMGB1 
       COX4I2, COL4A1, COL15A1, MADCAM1, FDCSP, CCL17, NTS, TNC, CDH5, FABP4 
       CAV1, COL4A2, CRIP2, RGS5, KLRB1, MYLK, IFITM1, IL33, PAPLN, HPN 
PC_ 5 
Positive:  APLNR, CDH5, CAV1, COL15A1, CRIP2, COL4A1, CCL21, KDR, CLDN5, MADCAM1 
       FABP4, IL33, COL4A2, CXCL13, TM4SF1, PODXL, CCL19, COX4I2, ESAM, BCAM 
       NTS, PAPLN, SPNS2, MYLK, C8orf4, ADGRF5, TM4SF18, TGM2, RP11-536O18.1, CXorf36 
Negative:  CSF1R, MS4A4A, CYBB, PLD4, MS4A6A, CD163, HCK, IGSF6, FOLR2, ADAP2 
       CD86, MS4A7, MARCH1, MRC1, F13A1, MPEG1, CD14, SPI1, HLA-DMB, GPR34 
       CD33, TGFBI, CLEC7A, TIMD4, CEBPA, SIGLEC1, CSF2RA, SLC15A3, LY86, AGR2 
orig.RNA.seu <- RunUMAP(orig.RNA.seu, dims=1:40)
10:43:01 UMAP embedding parameters a = 0.9922 b = 1.112
10:43:01 Read 8321 rows and found 40 numeric columns
10:43:01 Using Annoy for neighbor search, n_neighbors = 30
10:43:01 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:43:03 Writing NN index file to temp file /tmp/RtmpsI55IG/file18a72231550b
10:43:03 Searching Annoy index using 1 thread, search_k = 3000
10:43:06 Annoy recall = 100%
10:43:08 Commencing smooth kNN distance calibration using 1 thread
10:43:10 Initializing from normalized Laplacian + noise
10:43:11 Commencing optimization for 500 epochs, with 367644 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:43:32 Optimization finished
plotly::ggplotly(DimPlot(orig.RNA.seu, group.by="annotation"))
ggarrange(plotlist = map(cell.types[!cell.types %in% c( "NK","NA(1)","NA(3)","ILC3","SP (2)")] , ~ compareCluster(.x)), ncol=1) +
  ggsave(paste0(outdir, "umap_clusters.png"), height = 30, width = 10)
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

Prediction score

Quantifies the uncertainty of the prediction. Calculated differently for every method, but used to define which cells are “unassigned”.

orig.composition <- orig.RNA$annotation
orig.frac <- table(orig.composition)/length(orig.composition)
orig.frac.df <- data.frame(orig.frac) %>%
  dplyr::rename(predicted.id=orig.composition, frac.label=Freq) %>%
  mutate(method="original.RNA")
score_cols <- str_subset(colnames(atac.seu@meta.data), 'score_')
label_cols <- str_subset(colnames(atac.seu@meta.data), 'predicted.id_')
pred.labels.df <- imap(list(CCA=pred.cca, Liger=pred.liger, Conos=pred.conos), ~ 
      rownames_to_column(.x, "cell") %>%
      rename_all(funs(str_remove(., str_c("_",.y)))) %>%
      mutate(method=.y)
    ) %>%
  purrr::reduce(bind_rows) %>%
  mutate(score=ifelse(is.na(score), 0, score))
funs() is soft deprecated as of dplyr 0.8.0
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
This warning is displayed once per session.binding character and factor vector, coercing into character vector
predict_score_hist <- 
  pred.labels.df %>%
  ggplot(aes(score, fill=method)) +
  geom_histogram(position="identity", alpha=0.8, bins=40) +
  facet_grid(method ~.) +
  scale_fill_brewer(palette="Set1") +
  xlab("Label prediction score") +
  theme_bw(base_size = 16) +
  theme(legend.position = "top")
cutoffs <- seq(0,1,0.05)
predict_score_cumedist <-
  pred.labels.df %>%
  group_by(method) %>%
  mutate(bins=cut(score, breaks = cutoffs)) %>%
  mutate(score=as.numeric(str_remove_all(as.character(bins), ".+,|]"))) %>%
  ggplot(aes(score, color=method)) +
  stat_ecdf(size=0.8, alpha=0.7) +
  scale_color_brewer(palette = "Set1") +
  ylab("Fraction of unassigned cells") +
  xlab("Prediction score cutoff") +
  theme_bw(base_size = 16) +
  xlim(0,1) +
  coord_fixed() +
  guides(color="none") 
ggpubr::ggarrange(predict_score_hist, predict_score_cumedist, common.legend = TRUE, widths = c(0.8, 1.2),
          labels=c("A", "B")) +
  ggsave(paste0(outdir, "prediction_score_distribution.png"), height = 6, width = 10)
Removed 47 rows containing non-finite values (stat_ecdf).

ggpubr::ggarrange(
  plotlist = list(
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_CCA"  , coord.fixed = TRUE) + ggtitle("CCA"),
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Liger", coord.fixed = TRUE) + ggtitle("Liger"),
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Conos", coord.fixed = TRUE) + ggtitle("Conos")
  ),
  common.legend = TRUE, ncol=3, nrow=1
) +
  ggsave(paste0(outdir, "prediction_score_umaps.png"), height = 7, width=14)

Cell type composition

Compare cell type fractions (w uncertainty)

orig.rank.df <- orig.frac.df %>% 
  mutate(orig.rank=dense_rank(frac.label)) %>%
  select(orig.rank, predicted.id) %>%
  distinct() %>%
  arrange(orig.rank) %>%
  column_to_rownames("predicted.id") 
pred.labels.df %>%
  group_by(method) %>%
  drop_na() %>%
  mutate(tot.cells=n()) %>%
  ungroup() %>%
  group_by(method, predicted.id) %>%
  summarise(tot.label = n(), tot.cells = max(tot.cells), mean.score=mean(score)) %>%
  mutate(frac.label=tot.label/tot.cells) %>%
  bind_rows(orig.frac.df) %>%
  mutate(orig.rank = orig.rank.df[predicted.id,]) %>%
  mutate(predicted.id=factor(predicted.id, levels=rownames(orig.rank.df)))%>%
  # select(method, predicted.id, frac.label) %>%
  # distinct() %>%
  ggplot(aes(predicted.id, frac.label, fill=mean.score, color=mean.score)) +
  geom_point(size=2) +
  geom_col(width=0.05) +
  coord_flip() +
  # geom_line(aes(group=method)) +
  facet_wrap(method~., nrow=1, ncol=4, scales="free_x") +
  scale_color_viridis_c() +
  scale_fill_viridis_c() +
  ylab("Fraction of cells") +
  theme_bw(base_size = 16) +
  ggsave(paste0(outdir, "cell_type_composition_bars.png"), width = 15, height = 7)
binding character and factor vector, coercing into character vector

Agreement with unsupervised clustering of ATAC data

Calculate which fractions of NNs in bin based graph of ATAC cells have the same annotation

k = 30
atac.seu <- FindNeighbors(atac.seu, assay = "ATAC", reduction = "SnapATAC", dims = 1:15, k.param = k)
Computing nearest neighbor graph
Computing SNN
atac.nn.list <- getNNlist(atac.seu)
knn.score.CCA <- test.knn(atac.nn.list, setNames(pred.cca.filtered$predicted.id_CCA, rownames(pred.cca.filtered)))
p-value will be approximate in the presence of ties
knn.score.conos <- test.knn(atac.nn.list, setNames(pred.conos.filtered$predicted.id_Conos, rownames(pred.conos.filtered)))
p-value will be approximate in the presence of ties
knn.score.liger <- test.knn(atac.nn.list, setNames(pred.liger.filtered$predicted.id_Liger, rownames(pred.liger.filtered)))
p-value will be approximate in the presence of ties
knn_score_df <-
  list(CCA=knn.score.CCA, conos=knn.score.conos, liger=knn.score.liger) %>%
  imap( ~ data.frame(KNN_score = .x$KNN_score, D=.x$D, p.val=.x$p.val, method=.y)) %>%
  # imap( ~ data.frame(KNN_score = .x$KNN_score, cell= names(.x$KNN_score), D=.x$D, p.val=.x$p.val, method=.y)) %>%
  purrr::reduce(bind_rows) %>%
  dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score)) %>%
  mutate(data="true")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
knn_score_null_df <-
  list(CCA=knn.score.CCA, conos=knn.score.conos, liger=knn.score.liger) %>%
  imap( ~ data.frame(KNN_score = .x$null, D=.x$D, p.val=.x$p.val, method=.y)) %>%
  # imap( ~ data.frame(KNN_score = .x$KNN_score, cell= names(.x$KNN_score), D=.x$D, p.val=.x$p.val, method=.y)) %>%
  purrr::reduce(bind_rows) %>%
  dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score)) %>%
  mutate(data="null")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
bind_rows(knn_score_df, knn_score_null_df) %>%
  ggplot(aes(KNN_score, color=method)) +
  stat_ecdf( aes(alpha=data), size=1) +
  # stat_ecdf(data=. %>% filter(data=="true"), size=1) +
  facet_grid(method~.) +
  scale_alpha_discrete( range=c(0.5,1), name="") +
  scale_color_brewer(palette = "Set1") +
  geom_text(data=. %>% distinct(method, D, p.val), 
            x=1, y=0.05, hjust=1,
            aes(label=glue("KNN score = {round(D, 3)}, p.value: {p.val}"), y=c(0.90, 0.95, 1))) +
  theme_bw(base_size = 16) +
  ylab("ECDF") + xlab("Fraction of KNNs with shared label") +
  ggsave(paste(outdir,"KNN_score_ecdf_unionHVG.png"), height = 6, width=7)
Using alpha for a discrete variable is not advised.

pred.labels[which(pred.labels == clust)]
AACAGTCTCCATATCT-1 AACCTTTGTGCGTTTA-1 AAGGAGCGTTCGGGAA-1 CAAAGCTCAGAGATGC-1 CATTCATCAAGGGTAC-1 CCTTGGTAGTCCCTCT-1 CTTGCTGCATTGCACA-1 GAAGTGGAGGCCTCGT-1 
              "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC" 
GCACCTTAGCTGAGGT-1 GCTGTTCTCCCACTAC-1 TCTAGTTAGTAGGTCG-1 TTCATCACATAGCCAT-1 AAAGATGTCCACCTAC-1 AATGGCTCACAGGAAC-1 ACAGCGCCAGGTCCTG-1 ACATGCACACGTTGTA-1 
              "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC" 
ACGTTAGCACATATCG-1 ATAGTCGCATTTGTTC-1 ATGGATCTCGATGTAC-1 CAACGTATCGCAAACT-1 CAGTATGAGCTGCCAC-1 CATGTTTGTCTCTGCT-1 CATTCATTCAGAATGA-1 CATTCCGCATCCCTTG-1 
              "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC" 
CATTCCGTCCACTAGA-1 CCATACCAGGCTCAGA-1 CCTATTATCGCTGATA-1 CCTGGGACACCTGGTG-1 CCTTAATAGATTACGA-1 CGTGGCATCAATGCAC-1 CTAGCGGGTATTCGCA-1 CTAGGATAGCTTTCCC-1 
              "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC" 
CTCTACGCACGTTACA-1 GAAACAAAGAATCAAC-1 GAAGAGCCACTTACAG-1 GAGATTCAGGCATGCA-1 GCACGCAGTTTGCCCT-1 GCAGATTAGACGTCAG-1 GCATTCCAGCGTCAAG-1 GGAGTAGAGCCTGGTC-1 
              "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC" 
GGGTCTGCATACAACC-1 GGTACCGGTCCGCTTT-1 GTAATCGAGACTTGAA-1 GTCACAACATCGGCCA-1 GTGCACGCATAGAATG-1 GTTACGAAGAGGAACA-1 TAACGGTGTTTGATCG-1 TAACTTCAGCCTATAC-1 
              "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC" 
TACATGGGTCACTCTC-1 TACATGGGTTTGTAGC-1 TCAATTCCAGGTAGCA-1 TCACAGAGTGCGTAGA-1 TCACCACGTAGGGTCA-1 TCGAGCGGTATTGTCG-1 TCGCAGGTCATCGCAA-1 TCTATTGAGGTTCGAG-1 
              "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC" 
TGACTCCCAGCAAACG-1 TGATGCATCCTCATTA-1 TGCATTTAGTTTACGC-1 TGCTATTAGACCTATC-1 TGGCGCAGTTAGTAGA-1 TGGGTTAGTCTGGATT-1 TGTACAGAGCACCATT-1 TGTGTCCTCAAGAGAT-1 
              "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC"               "DC" 
TTACGTTGTGAGTCGA-1 TTCAACTAGTTACACC-1 TTCGATTTCCAGAGAG-1 TTGCGGGTCCATCATT-1 TTTGGTTAGGAAACTT-1 
              "DC"               "DC"               "DC"               "DC"               "DC" 

Accessibility of markers

Taking markers from Fig. S2 of JP’s manuscript

thymus.markers <- c("PTPRC", "CD3G", "TYROBP","CD19","HOXA9",'FXYD2',"SH3TC1","CCR9","CD8A", "CD8B","PDCD1", "CRTAM","CD40LG","CCR6","FOXP3","SOX13","ZNF683","KLRD1","TNFSF11","VPREB1","MS4A1", "CLEC9A", "CLEC10A", "LAMP3", "IL3RA", "FCGR3B", "C2","TPSB2",
                    'ITGA2B',"GYPA", "CDH5", "RGS5","CDH1", "PDGFRA","CRABP1")
# pbmc.markers <- c("CD79A", "MS4A1", "CD8A", "CD8B", "LYZ")
# thymus.markers <- list(Fb=c("PDGFRA", "COLEC11", "FBN1", "PI16"),
#                        VSMC=c("PDGFRB", 'ACTA2', "RGS5"),
#                        Endo=c("PECAM1", "CDH5","LYVE1"),
#                        TEC = c("EPCAM", "FOXN1", "CCL25", "CCL19")
#                        )
thymus.markers.df <- imap(thymus.markers, ~ data.frame(gene=.x, cell.type.class=.y)) %>%
  purrr::reduce(bind_rows)
marker.access.df <- atac.seu@assays$ACTIVITY@data[intersect(thymus.markers, rownames(atac.seu@assays$ACTIVITY)),] %>%
  as.matrix() %>%
  reshape2::melt(varnames=c("gene", "cell"), value.name="log.counts") %>%
  full_join(rownames_to_column(atac.seu@meta.data[, label_cols], "cell")) %>%
  # full_join(thymus.markers.df) %>%
  pivot_longer(cols=label_cols, names_to = "method", values_to = "predicted.id") %>%
  dplyr::mutate(method=str_remove(method,".+_")) %>%
  filter(method %in% c("CCA", "Liger", "Conos")) 
ordered_cell_types <- c("DN", "DP (Q)", "DP (P)", "SP", "NK", "ILC3", "DC", "Mac", "Ery", "Fib")
markers_pl <- 
  marker.access.df %>%
  mutate(predicted.id = case_when(str_detect(predicted.id, "CD8") ~ "CD8+T",
                                  # str_detect(predicted.id, "CD4") ~ "CD4+T",
                                  TRUE ~ predicted.id
                                  )
         ) %>%
  mutate(predicted.id=factor(predicted.id, levels = ordered_cell_types)) %>%
  group_by(method, predicted.id, gene) %>%
  dplyr::mutate(frac.cells=sum(log.counts > 0)/n()) %>%
  # filter(method=="CCA") %>%
  ungroup() %>%
  ggplot( aes( gene, predicted.id)) +
  geom_point(aes(size=frac.cells, color=frac.cells)) +
  facet_grid(method~., space="free", scales="free_x") +
  scale_color_gradient(high="darkblue", low="white") +
  # scale_color_viridis_c() +
  theme_bw(base_size = 16) +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
        strip.text.x = element_text(angle=45)) 
markers_pl 

  
ggsave(paste0(outdir, "Thymus_markers_accessibility.png"), height = 16, width = 12)

Reproducing Fig.2H on T-cell development

t.cell.markers <- list(known.markers = c("CD34", "IGLL1", "TRGC2", "TRDC", "PTCRA", "TRBC2", "TRAC", "CD4", "CD8A", "CD8B"),
                       chemokine.receptors = c("CCR9", "CCR7"),
                       tcr.activation = c("CD5", "CD27"),
                       proliferation=c("PCNA", "CDK1", "MKI67"),
                       cyclin.D = c("CCND2", "CCND3"),
                       recombination=c("RAG1", "RAG2"),
                       apoptosis=c("HRK","BMF", "TP53INP1"),
                       stage.markers = c("ST18", "HIVEP3", "RGPD3", "SMPD3", "AQP3", "RORC", "SATB1", "TOX2")
                       ) 
t.cell.markers.df <- imap(t.cell.markers, ~ data.frame(gene=.x, cell.type.class=.y)) %>%
  purrr::reduce(bind_rows)
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
ordered.tcells <- c("DN", "DP (P)", "DP (Q)","SP (1)")
tcells.markers.df <- 
  atac.seu@assays$ACTIVITY@data[intersect(unlist(t.cell.markers), rownames(atac.seu@assays$ACTIVITY)),] %>%
  as.matrix() %>%
  reshape2::melt(varnames=c("gene", "cell"), value.name="log.counts") %>%
  full_join(rownames_to_column(atac.seu@meta.data[, label_cols], "cell")) %>%
  pivot_longer(cols=label_cols, names_to = "method", values_to = "predicted.id") %>%
  dplyr::mutate(method=str_remove(method,".+_")) %>%
  filter(method %in% c("CCA", "Liger", "Conos")) %>%
  mutate(predicted.id=ifelse(str_detect(predicted.id, "CD8+"), "CD8+T", predicted.id)) %>%
  mutate(predicted.id=ifelse(str_detect(predicted.id, "CD4+"), "CD4+T", predicted.id)) %>%
  filter(predicted.id %in% ordered.tcells) %>%
  group_by(method, predicted.id, gene) %>%
  dplyr::mutate(frac.cells=sum(log.counts > 0)/n(), mean.acc=mean(log.counts)) %>%
  ungroup() 
Joining, by = "cell"
Column `cell` joining factor and character vector, coercing into character vector
tcells.markers.df %>%
  full_join(t.cell.markers.df) %>%
  # filter(method=="CCA") %>%
  mutate(predicted.id=factor(predicted.id, levels=ordered.tcells)) %>%
  ggplot(aes( predicted.id, gene)) +
  facet_grid(cell.type.class~method, scales = "free_y", space="free") +
  geom_point(aes(size=frac.cells, color=mean.acc)) +
  scale_color_gradient(high="darkblue", low="white") +
  # scale_color_gradient2(midpoint = 0.5) +
  theme_bw(base_size = 16) +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
        strip.text.y = element_text(angle=0)) 
Joining, by = "gene"
Column `gene` joining factor and character vector, coercing into character vector

ggsave(paste0(outdir, "tcell_markers.png"), height = 14, width = 14)

Thoughts

  • Conos scores a lot of cells with high confidence, but fails to assign cells to difficult clusters
  • CCA resembles the composition of the RNA data better, but curious that the other methods identify way more
LS0tCnRpdGxlOiAiTGFiZWwgdHJhbnNmZXIgRURBIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KFNuYXBBVEFDKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEZXNjVG9vbHMpICAjIDQgQVVDIGZ1bmN0aW9uCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShnZ2FsbHV2aWFsKSAgIyA0IHJpdmVyIHBsb3QKbGlicmFyeShnZ3B1YnIpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL3V0aWxzLlIiKQpzb3VyY2UoIn4vbXVsdGlPbWljX2JlbmNobWFyay9LTk5fYWdyZWVtZW50LlIiKQoKCmdnX2NvbG9yX2h1ZSA8LSBmdW5jdGlvbihuKSB7CiAgaHVlcyA9IHNlcSgxNSwgMzc1LCBsZW5ndGggPSBuICsgMSkKICBoY2woaCA9IGh1ZXMsIGwgPSA2NSwgYyA9IDEwMClbMTpuXQp9CgojIyBNYWtlIG91dHB1dCBkaXJlY3RvcnkKb3V0ZGlyIDwtICJ+L211bHRpT21pY19iZW5jaG1hcmsvcmVwb3J0L291dHB1dC8yMDE5MTExM19sYWJlbFRyYW5zZmVyRURBX0Y3NF92Mi8iCmlmZWxzZSghZGlyLmV4aXN0cyhvdXRkaXIpLCBkaXIuY3JlYXRlKG91dGRpciksIEZBTFNFKQpgYGAKCgpgYGB7cn0KIyBtb2RlbC5jY2EgPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxDQ0FfdW5pb25faHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTEzLlJEUyIpCiMgbW9kZWwubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxMaWdlcl91bmlvbl9odmdfRjc0X1NDRWxpc3RfMjAxOTExMTMuUkRTIikKIyBtb2RlbC5jb25vcyA8LSByZWFkUkRTKCJ+L21vZGVscy9tb2RlbENvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQoKc2V1LmNjYSA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyQ0NBX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQpzZXUubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckxpZ2VyX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQpzZXUuY29ub3MgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQoKCmludGVncmF0ZV9mZWF0dXJlcyA8LSBzY2FuKCJ+L2ludEZlYXR1cmVzX3VuaW9uX2h2Z18yMDAwX0Y3NF9TQ0VsaXN0XzIwMTkxMTEzLnR4dCIsIHdoYXQ9JycpCgppbnQubGlzdCA8LSBsaXN0KENDQT1zZXUuY2NhLCBMaWdlcj1zZXUubGlnZXIsIENvbm9zPXNldS5jb25vcykKCiMgIyMgTWFrZSBtZXRob2QgY29sb3IgcGFsZXR0ZQojIG1ldGhvZC5wYWxldHRlIDwtIGJyZXdlcl9wYWxldHRlXzRfdmFsdWVzKG5hbWVzKGludC5saXN0KSwgIlNldDEiKQoKYGBgCgojIyMgRW1iZWRkaW5ncwpWaXN1YWxpemUgbGFiZWwgdHJhbnNmZXIgb24gb3JpZ2luYWwgQVRBQyBkYXRhIChlbWJlZGRlZCBTbmFwQVRBQyBiaW5zKQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyMgTG9hZCBvcmlnaW5hbCBkYXRhCm9yaWcuQVRBQyA8LSByZWFkUkRTKCJ+L215X2RhdGEvY2VsbHJhbmdlci1hdGFjMTEwX2NvdW50XzMwNDM5X1dTU1M4MDM4MzYwX0dSQ2gzOC0xXzFfMC5zbmFwQVRBQy5SRFMiKQpzY2UubGlzdCA8LSByZWFkUkRTKCJ+L215X2RhdGEvaW50ZWdyYXRlZF90aHltdXMvRjc0X1NDRWxpc3RfMjAxOTExMTkuUkRTIikKb3JpZy5STkEgPC0gc2NlLmxpc3QkUk5BCgojIyBNYWtlIFNldXJhdE9iamVjdHMKYXRhYy5zZXUgPC0gc25hcFRvU2V1cmF0KAogICAgb2JqPW9yaWcuQVRBQywgCiAgICBlaWdzLmRpbXM9MToyMCwgCiAgICBub3JtPVRSVUUsCiAgICBzY2FsZT1UUlVFCiAgICApCmF0YWMuc2V1IDwtIFJlbmFtZUNlbGxzKGF0YWMuc2V1LCBuZXcubmFtZXMgPSBvcmlnLkFUQUNAbWV0YURhdGEkYmFyY29kZSkKCiMjIEFkZCBjZWxsIHR5cGUgcHJlZGljdGlvbnMKZ2V0UHJlZGljdGVkTGFiZWxzIDwtIGZ1bmN0aW9uKHNldS5pbnQsIGludC5uYW1lLCBpZC5jb2w9InByZWRpY3RlZC5pZCIsIHNjb3JlLmNvbD0ic2NvcmUiLCBmaWx0ZXJfc2NvcmU9MCl7CiAgcHJlZC5kZiA8LSBzZXUuaW50JEFUQUNAbWV0YS5kYXRhWyxjKGlkLmNvbCwgc2NvcmUuY29sKSwgZHJvcD1GXSAKICBjb2xuYW1lcyhwcmVkLmRmKSA8LSBjKCdwcmVkaWN0ZWQuaWQnLCAic2NvcmUiKQogIHByZWQuZGYgPC0gcHJlZC5kZiAlPiUKICAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JQogICAgbXV0YXRlKHByZWRpY3RlZC5pZCA9IGlmZWxzZShzY29yZSA8IGZpbHRlcl9zY29yZSwgTkEsIGFzLmNoYXJhY3RlcihwcmVkaWN0ZWQuaWQpKSkgJT4lCiAgICBjb2x1bW5fdG9fcm93bmFtZXMoImNlbGwiKQogIHJvd25hbWVzKHByZWQuZGYpIDwtIHN0cl9yZW1vdmUocm93bmFtZXMocHJlZC5kZiksICJeQVRBQ18iKQogIGNvbG5hbWVzKHByZWQuZGYpIDwtIGMoc3RyX2MoInByZWRpY3RlZC5pZCIsICJfIiwgaW50Lm5hbWUpLCBzdHJfYygic2NvcmUiLCAiXyIsIGludC5uYW1lKSkKICBwcmVkLmRmCiAgfQoKcHJlZC5jY2EgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jY2EsICJDQ0EiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKQpwcmVkLmxpZ2VyIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUubGlnZXIsICJMaWdlciIpCnByZWQuY29ub3MgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jb25vcywgIkNvbm9zIikKCmlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsKICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYSwgcHJlZC5saWdlciwgcHJlZC5jb25vcykpCn0gZWxzZSB7CiAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpCn0KYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTh9CiMjIG1ha2UgY2VsbCB0eXBlIHBhbGV0dGUKY2VsbC50eXBlcyA8LSBsZXZlbHMoc2V1LmNjYSRSTkEkYW5ub3RhdGlvbikKY2VsbC50eXBlLnBhbCA8LSBicmV3ZXJfcGFsZXR0ZV80X3ZhbHVlcyhjZWxsLnR5cGVzLCBwYWxldHRlID0gIlNldDEiKSAlPiUgc2V0TmFtZXMoY2VsbC50eXBlcykKYXRhYy5zZXUgPC0gUnVuVU1BUChhdGFjLnNldSwgcmVkdWN0aW9uID0gIlNuYXBBVEFDIiwgcmVkdWN0aW9uLm5hbWUgPSAidW1hcC5zbmFwIiwgZGltcz0xOjIwKQoKZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcGxvdGxpc3QgPSBsaXN0KAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiICAsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfTGlnZXIiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiTGlnZXIiKSwKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ29ub3MiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQogICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xCikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidW1hcF9sYWJlbHMucG5nIiksIHdpZHRoPTE2LCBoZWlnaHQgPSA4KQoKCmBgYAoKRmlsdGVyIGxvdyBjb25maWRlbmNlIGNhbGxzIApgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTh9CnByZWQuY2NhLmZpbHRlcmVkIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY2NhLCAiQ0NBIiwgc2NvcmUuY29sID0gInByZWRpY3Rpb24uc2NvcmUubWF4IiwgZmlsdGVyX3Njb3JlID0gMC41KQpwcmVkLmxpZ2VyLmZpbHRlcmVkIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUubGlnZXIsICJMaWdlciIsIGZpbHRlcl9zY29yZSA9IDAuNSkKcHJlZC5jb25vcy5maWx0ZXJlZCA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmNvbm9zLCAiQ29ub3MiLCBmaWx0ZXJfc2NvcmUgPSAwLjUpCgppZiAoYWxsKHJvd25hbWVzKHByZWQuY29ub3MpID09IHJvd25hbWVzKHByZWQuY2NhKSkgJiBhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5saWdlcikpKSB7CiAgYXRhYy5zZXUgPC0gQWRkTWV0YURhdGEoYXRhYy5zZXUsIG1ldGFkYXRhID0gY2JpbmQocHJlZC5jY2EuZmlsdGVyZWQsIHByZWQubGlnZXIuZmlsdGVyZWQsIHByZWQuY29ub3MuZmlsdGVyZWQpKQp9IGVsc2UgewogIHN0b3AoIk5vbiBjb3JyZXNwb25kaW5nIGNlbGwgbmFtZXMiKQp9CgpnZ3B1YnI6OmdnYXJyYW5nZSgKICBwbG90bGlzdCA9IGxpc3QoCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQSIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgcmVwZWw9VFJVRSkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNlbGwudHlwZS5wYWwsIG5hLnZhbHVlPSJncmV5ODAiKSArCiAgICAgIGdndGl0bGUoIkNDQSIpLAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9MaWdlciIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgcmVwZWw9VFJVRSkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNlbGwudHlwZS5wYWwsIG5hLnZhbHVlPSJncmV5ODAiKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0Nvbm9zIiwgY29scz1jZWxsLnR5cGUucGFsLCByZXBlbD1UUlVFKSArIAogICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2VsbC50eXBlLnBhbCwgbmEudmFsdWU9ImdyZXk4MCIpICsgZ2d0aXRsZSgiQ29ub3MiKQogICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xCikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidW1hcF9sYWJlbHNfZmlsdGVyZWQucG5nIiksIHdpZHRoPTE2LCBoZWlnaHQgPSA4KQoKYGBgCgoKYGBge3J9CnBsIDwtICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQSIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKQpwbG90bHk6OmdncGxvdGx5KHBsKQpgYGAKCmBgYHtyfQpwbCA8LSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9Db25vcyIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDb25vcyIpCnBsb3RseTo6Z2dwbG90bHkocGwpCmBgYAoKYGBge3J9Cm9yaWcuUk5BLnNldSA8LSBhcy5TZXVyYXQob3JpZy5STkEpCm9yaWcuUk5BLnNldSA8LSBGaW5kVmFyaWFibGVGZWF0dXJlcyhvcmlnLlJOQS5zZXUpCm9yaWcuUk5BLnNldSA8LSBTY2FsZURhdGEob3JpZy5STkEuc2V1KQpvcmlnLlJOQS5zZXUgPC0gUnVuUENBKG9yaWcuUk5BLnNldSkKb3JpZy5STkEuc2V1IDwtIFJ1blVNQVAob3JpZy5STkEuc2V1LCBkaW1zPTE6NDApCgpwbG90bHk6OmdncGxvdGx5KERpbVBsb3Qob3JpZy5STkEuc2V1LCBncm91cC5ieT0iYW5ub3RhdGlvbiIpKQpgYGAKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0yMH0KY29tcGFyZUNsdXN0ZXIgPC0gZnVuY3Rpb24oY2x1c3QpewogIHBsb3RsaXN0IDwtIG1hcChsaXN0KCJDQ0EiLCAiTGlnZXIiLCAiQ29ub3MiKSwgCiAgICAgICAgICAgICAgICAgIH4gRmVhdHVyZVBsb3RDbHVzdGVyKGF0YWMuc2V1LCBhbm5vdGF0aW9uX2NvbCA9IGdsdWUoJ3ByZWRpY3RlZC5pZF97Lnh9JyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVfY29sPWdsdWUoInNjb3JlX3sueH0iKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyPWNsdXN0LCBsYWJlbD1nbHVlKCd7Y2x1c3R9IC0gey54fScpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgKQogIHJuYV9wbG90IDwtIERpbVBsb3RDbHVzdGVyKG9yaWcuUk5BLnNldSwgYW5ub3RhdGlvbl9jb2wgPSAiYW5ub3RhdGlvbiIsIGNsdXN0ZXIgPSBjbHVzdCwgbGFiZWw9Z2x1ZSgie2NsdXN0fSAtIFJOQSIpLCByZWR1Y3QgPSAidW1hcCIpCiAgcGxvdGxpc3RbWzRdXSA8LSBybmFfcGxvdAogIGdnYXJyYW5nZShwbG90bGlzdCA9IHBsb3RsaXN0LCBucm93PTEpCiAgfQoKZ2dhcnJhbmdlKHBsb3RsaXN0ID0gbWFwKGNlbGwudHlwZXNbIWNlbGwudHlwZXMgJWluJSBjKCAiTksiLCJOQSgxKSIsIk5BKDMpIiwiSUxDMyIsIlNQICgyKSIpXSAsIH4gY29tcGFyZUNsdXN0ZXIoLngpKSwgbmNvbD0xKSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bWFwX2NsdXN0ZXJzLnBuZyIpLCBoZWlnaHQgPSAzMCwgd2lkdGggPSAxMCkKYGBgCgojIyBQcmVkaWN0aW9uIHNjb3JlClF1YW50aWZpZXMgdGhlIHVuY2VydGFpbnR5IG9mIHRoZSBwcmVkaWN0aW9uLiBDYWxjdWxhdGVkIGRpZmZlcmVudGx5IGZvciBldmVyeSBtZXRob2QsIGJ1dCB1c2VkIHRvIGRlZmluZSB3aGljaCBjZWxscyBhcmUgInVuYXNzaWduZWQiLgoKCmBgYHtyfQpvcmlnLmNvbXBvc2l0aW9uIDwtIG9yaWcuUk5BJGFubm90YXRpb24Kb3JpZy5mcmFjIDwtIHRhYmxlKG9yaWcuY29tcG9zaXRpb24pL2xlbmd0aChvcmlnLmNvbXBvc2l0aW9uKQoKb3JpZy5mcmFjLmRmIDwtIGRhdGEuZnJhbWUob3JpZy5mcmFjKSAlPiUKICBkcGx5cjo6cmVuYW1lKHByZWRpY3RlZC5pZD1vcmlnLmNvbXBvc2l0aW9uLCBmcmFjLmxhYmVsPUZyZXEpICU+JQogIG11dGF0ZShtZXRob2Q9Im9yaWdpbmFsLlJOQSIpCgpzY29yZV9jb2xzIDwtIHN0cl9zdWJzZXQoY29sbmFtZXMoYXRhYy5zZXVAbWV0YS5kYXRhKSwgJ3Njb3JlXycpCmxhYmVsX2NvbHMgPC0gc3RyX3N1YnNldChjb2xuYW1lcyhhdGFjLnNldUBtZXRhLmRhdGEpLCAncHJlZGljdGVkLmlkXycpCgpwcmVkLmxhYmVscy5kZiA8LSBpbWFwKGxpc3QoQ0NBPXByZWQuY2NhLCBMaWdlcj1wcmVkLmxpZ2VyLCBDb25vcz1wcmVkLmNvbm9zKSwgfiAKICAgICAgcm93bmFtZXNfdG9fY29sdW1uKC54LCAiY2VsbCIpICU+JQogICAgICByZW5hbWVfYWxsKGZ1bnMoc3RyX3JlbW92ZSguLCBzdHJfYygiXyIsLnkpKSkpICU+JQogICAgICBtdXRhdGUobWV0aG9kPS55KQogICAgKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgbXV0YXRlKHNjb3JlPWlmZWxzZShpcy5uYShzY29yZSksIDAsIHNjb3JlKSkKCnByZWRpY3Rfc2NvcmVfaGlzdCA8LSAKICBwcmVkLmxhYmVscy5kZiAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBmaWxsPW1ldGhvZCkpICsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjgsIGJpbnM9NDApICsKICBmYWNldF9ncmlkKG1ldGhvZCB+LikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgeGxhYigiTGFiZWwgcHJlZGljdGlvbiBzY29yZSIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKY3V0b2ZmcyA8LSBzZXEoMCwxLDAuMDUpCnByZWRpY3Rfc2NvcmVfY3VtZWRpc3QgPC0KICBwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIG11dGF0ZShiaW5zPWN1dChzY29yZSwgYnJlYWtzID0gY3V0b2ZmcykpICU+JQogIG11dGF0ZShzY29yZT1hcy5udW1lcmljKHN0cl9yZW1vdmVfYWxsKGFzLmNoYXJhY3RlcihiaW5zKSwgIi4rLHxdIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgc3RhdF9lY2RmKHNpemU9MC44LCBhbHBoYT0wLjcpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIHlsYWIoIkZyYWN0aW9uIG9mIHVuYXNzaWduZWQgY2VsbHMiKSArCiAgeGxhYigiUHJlZGljdGlvbiBzY29yZSBjdXRvZmYiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB4bGltKDAsMSkgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGd1aWRlcyhjb2xvcj0ibm9uZSIpIAoKZ2dwdWJyOjpnZ2FycmFuZ2UocHJlZGljdF9zY29yZV9oaXN0LCBwcmVkaWN0X3Njb3JlX2N1bWVkaXN0LCBjb21tb24ubGVnZW5kID0gVFJVRSwgd2lkdGhzID0gYygwLjgsIDEuMiksCiAgICAgICAgICBsYWJlbHM9YygiQSIsICJCIikpICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInByZWRpY3Rpb25fc2NvcmVfZGlzdHJpYnV0aW9uLnBuZyIpLCBoZWlnaHQgPSA2LCB3aWR0aCA9IDEwKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9OH0KZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcGxvdGxpc3QgPSBsaXN0KAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0NDQSIgICwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkNDQSIpLAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0xpZ2VyIiwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBGZWF0dXJlUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGZlYXR1cmUgPSAic2NvcmVfQ29ub3MiLCBjb29yZC5maXhlZCA9IFRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQogICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xCikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAicHJlZGljdGlvbl9zY29yZV91bWFwcy5wbmciKSwgaGVpZ2h0ID0gNywgd2lkdGg9MTQpCmBgYAoKCgojIyBDZWxsIHR5cGUgY29tcG9zaXRpb24KCkNvbXBhcmUgY2VsbCB0eXBlIGZyYWN0aW9ucyAodyB1bmNlcnRhaW50eSkKCmBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9N30Kb3JpZy5yYW5rLmRmIDwtIG9yaWcuZnJhYy5kZiAlPiUgCiAgbXV0YXRlKG9yaWcucmFuaz1kZW5zZV9yYW5rKGZyYWMubGFiZWwpKSAlPiUKICBzZWxlY3Qob3JpZy5yYW5rLCBwcmVkaWN0ZWQuaWQpICU+JQogIGRpc3RpbmN0KCkgJT4lCiAgYXJyYW5nZShvcmlnLnJhbmspICU+JQogIGNvbHVtbl90b19yb3duYW1lcygicHJlZGljdGVkLmlkIikgCgpwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIGRyb3BfbmEoKSAlPiUKICBtdXRhdGUodG90LmNlbGxzPW4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkKSAlPiUKICBzdW1tYXJpc2UodG90LmxhYmVsID0gbigpLCB0b3QuY2VsbHMgPSBtYXgodG90LmNlbGxzKSwgbWVhbi5zY29yZT1tZWFuKHNjb3JlKSkgJT4lCiAgbXV0YXRlKGZyYWMubGFiZWw9dG90LmxhYmVsL3RvdC5jZWxscykgJT4lCiAgYmluZF9yb3dzKG9yaWcuZnJhYy5kZikgJT4lCiAgbXV0YXRlKG9yaWcucmFuayA9IG9yaWcucmFuay5kZltwcmVkaWN0ZWQuaWQsXSkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1mYWN0b3IocHJlZGljdGVkLmlkLCBsZXZlbHM9cm93bmFtZXMob3JpZy5yYW5rLmRmKSkpJT4lCiAgIyBzZWxlY3QobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGZyYWMubGFiZWwpICU+JQogICMgZGlzdGluY3QoKSAlPiUKICBnZ3Bsb3QoYWVzKHByZWRpY3RlZC5pZCwgZnJhYy5sYWJlbCwgZmlsbD1tZWFuLnNjb3JlLCBjb2xvcj1tZWFuLnNjb3JlKSkgKwogIGdlb21fcG9pbnQoc2l6ZT0yKSArCiAgZ2VvbV9jb2wod2lkdGg9MC4wNSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgIyBnZW9tX2xpbmUoYWVzKGdyb3VwPW1ldGhvZCkpICsKICBmYWNldF93cmFwKG1ldGhvZH4uLCBucm93PTEsIG5jb2w9NCwgc2NhbGVzPSJmcmVlX3giKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogIHlsYWIoIkZyYWN0aW9uIG9mIGNlbGxzIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJjZWxsX3R5cGVfY29tcG9zaXRpb25fYmFycy5wbmciKSwgd2lkdGggPSAxNSwgaGVpZ2h0ID0gNykKYGBgCgoKPCEtLSBEb2VzIHRoZSB1bmNlcnRhaW50eSBkZXBlbmQgb24gdGhlIHNpemUgb2YgdGhlIGNsdXN0ZXI/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9NX0gLS0+Cgo8IS0tIHByZWQubGFiZWxzLmRmICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KG1ldGhvZCkgJT4lIC0tPgo8IS0tICAgZHJvcF9uYSgpICU+JSAtLT4KPCEtLSAgIG11dGF0ZSh0b3QuY2VsbHM9bigpKSAlPiUgLS0+CjwhLS0gICB1bmdyb3VwKCkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZSh0b3QubGFiZWwgPSBuKCksIHRvdC5jZWxscyA9IG1heCh0b3QuY2VsbHMpLCBtZWFuLnNjb3JlPW1lZGlhbihzY29yZSksIHNkLnNjb3JlPW1hZChzY29yZSkpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShmcmFjLmxhYmVsPXRvdC5sYWJlbC90b3QuY2VsbHMpICU+JSAtLT4KPCEtLSAgICMgYmluZF9yb3dzKG9yaWcuZnJhYy5kZikgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhmcmFjLmxhYmVsLCBtZWFuLnNjb3JlLCBjb2xvcj1tZXRob2QpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChzaXplPTIpICsgLS0+CjwhLS0gICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPW1lYW4uc2NvcmUtc2Quc2NvcmUsIHltYXg9bWVhbi5zY29yZStzZC5zY29yZSksIGFscGhhPTAuNikgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKyAtLT4KPCEtLSAgICMgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgc3Bhbj0xLjIpICsgLS0+CjwhLS0gICBmYWNldF9ncmlkKC4gfiBtZXRob2QpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKyAtLT4KPCEtLSAgIHN0YXRfY29yKGxhYmVsLnggPSAwLjIsIGxhYmVsLnk9MC4yNSwgY29sb3I9ImJsYWNrIiwgc2l6ZT01KSAgLS0+CgoKPCEtLSBgYGAgLS0+CgojIyMgQWdyZWVtZW50IHdpdGggdW5zdXBlcnZpc2VkIGNsdXN0ZXJpbmcgb2YgQVRBQyBkYXRhCkNhbGN1bGF0ZSB3aGljaCBmcmFjdGlvbnMgb2YgTk5zIGluIGJpbiBiYXNlZCBncmFwaCBvZiBBVEFDIGNlbGxzIGhhdmUgdGhlIHNhbWUgYW5ub3RhdGlvbgpgYGB7cn0KayA9IDMwCmF0YWMuc2V1IDwtIEZpbmROZWlnaGJvcnMoYXRhYy5zZXUsIGFzc2F5ID0gIkFUQUMiLCByZWR1Y3Rpb24gPSAiU25hcEFUQUMiLCBkaW1zID0gMToxNSwgay5wYXJhbSA9IGspCgphdGFjLm5uLmxpc3QgPC0gZ2V0Tk5saXN0KGF0YWMuc2V1KQoKa25uLnNjb3JlLkNDQSA8LSB0ZXN0LmtubihhdGFjLm5uLmxpc3QsIHNldE5hbWVzKHByZWQuY2NhLmZpbHRlcmVkJHByZWRpY3RlZC5pZF9DQ0EsIHJvd25hbWVzKHByZWQuY2NhLmZpbHRlcmVkKSkpCmtubi5zY29yZS5jb25vcyA8LSB0ZXN0LmtubihhdGFjLm5uLmxpc3QsIHNldE5hbWVzKHByZWQuY29ub3MuZmlsdGVyZWQkcHJlZGljdGVkLmlkX0Nvbm9zLCByb3duYW1lcyhwcmVkLmNvbm9zLmZpbHRlcmVkKSkpCmtubi5zY29yZS5saWdlciA8LSB0ZXN0LmtubihhdGFjLm5uLmxpc3QsIHNldE5hbWVzKHByZWQubGlnZXIuZmlsdGVyZWQkcHJlZGljdGVkLmlkX0xpZ2VyLCByb3duYW1lcyhwcmVkLmxpZ2VyLmZpbHRlcmVkKSkpCgprbm5fc2NvcmVfZGYgPC0KICBsaXN0KENDQT1rbm4uc2NvcmUuQ0NBLCBjb25vcz1rbm4uc2NvcmUuY29ub3MsIGxpZ2VyPWtubi5zY29yZS5saWdlcikgJT4lCiAgaW1hcCggfiBkYXRhLmZyYW1lKEtOTl9zY29yZSA9IC54JEtOTl9zY29yZSwgRD0ueCRELCBwLnZhbD0ueCRwLnZhbCwgbWV0aG9kPS55KSkgJT4lCiAgIyBpbWFwKCB+IGRhdGEuZnJhbWUoS05OX3Njb3JlID0gLngkS05OX3Njb3JlLCBjZWxsPSBuYW1lcygueCRLTk5fc2NvcmUpLCBEPS54JEQsIHAudmFsPS54JHAudmFsLCBtZXRob2Q9LnkpKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSkpICU+JQogIG11dGF0ZShkYXRhPSJ0cnVlIikKa25uX3Njb3JlX251bGxfZGYgPC0KICBsaXN0KENDQT1rbm4uc2NvcmUuQ0NBLCBjb25vcz1rbm4uc2NvcmUuY29ub3MsIGxpZ2VyPWtubi5zY29yZS5saWdlcikgJT4lCiAgaW1hcCggfiBkYXRhLmZyYW1lKEtOTl9zY29yZSA9IC54JG51bGwsIEQ9LngkRCwgcC52YWw9LngkcC52YWwsIG1ldGhvZD0ueSkpICU+JQogICMgaW1hcCggfiBkYXRhLmZyYW1lKEtOTl9zY29yZSA9IC54JEtOTl9zY29yZSwgY2VsbD0gbmFtZXMoLngkS05OX3Njb3JlKSwgRD0ueCRELCBwLnZhbD0ueCRwLnZhbCwgbWV0aG9kPS55KSkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICU+JQogIGRwbHlyOjptdXRhdGUoS05OX3Njb3JlPWlmZWxzZShpcy5uYShLTk5fc2NvcmUpLCAwLCBLTk5fc2NvcmUpKSAlPiUKICBtdXRhdGUoZGF0YT0ibnVsbCIpCgoKYmluZF9yb3dzKGtubl9zY29yZV9kZiwga25uX3Njb3JlX251bGxfZGYpICU+JQogIGdncGxvdChhZXMoS05OX3Njb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgc3RhdF9lY2RmKCBhZXMoYWxwaGE9ZGF0YSksIHNpemU9MSkgKwogICMgc3RhdF9lY2RmKGRhdGE9LiAlPiUgZmlsdGVyKGRhdGE9PSJ0cnVlIiksIHNpemU9MSkgKwogIGZhY2V0X2dyaWQobWV0aG9kfi4pICsKICBzY2FsZV9hbHBoYV9kaXNjcmV0ZSggcmFuZ2U9YygwLjUsMSksIG5hbWU9IiIpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGdlb21fdGV4dChkYXRhPS4gJT4lIGRpc3RpbmN0KG1ldGhvZCwgRCwgcC52YWwpLCAKICAgICAgICAgICAgeD0xLCB5PTAuMDUsIGhqdXN0PTEsCiAgICAgICAgICAgIGFlcyhsYWJlbD1nbHVlKCJLTk4gc2NvcmUgPSB7cm91bmQoRCwgMyl9LCBwLnZhbHVlOiB7cC52YWx9IiksIHk9YygwLjkwLCAwLjk1LCAxKSkpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHlsYWIoIkVDREYiKSArIHhsYWIoIkZyYWN0aW9uIG9mIEtOTnMgd2l0aCBzaGFyZWQgbGFiZWwiKSArCiAgZ2dzYXZlKHBhc3RlKG91dGRpciwiS05OX3Njb3JlX2VjZGZfdW5pb25IVkcucG5nIiksIGhlaWdodCA9IDYsIHdpZHRoPTcpCmBgYApgYGB7cn0KY2x1c3QgPSAiREMiCnByZWQubGFiZWxzIDwtIHNldE5hbWVzKHByZWQuY2NhLmZpbHRlcmVkJHByZWRpY3RlZC5pZF9DQ0EsIHJvd25hbWVzKHByZWQuY2NhLmZpbHRlcmVkKSkgCnByZWQubGFiZWxzW3doaWNoKHByZWQubGFiZWxzID09IGNsdXN0KV0KS05OX3Njb3JlKGF0YWMubm4ubGlzdCwgKQpgYGAKCgo8IS0tICMjIyMgV2hpY2ggY2VsbHMgYXJlIGluY29uc2lzdGVudGx5IGFsaWduZWQ/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9MTB9IC0tPgo8IS0tIHByZWQubGFiZWxzLmRmICU+JSAtLT4KPCEtLSAgIHNlbGVjdChtZXRob2QsIHByZWRpY3RlZC5pZCwgY2VsbCkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2UoaXMubmEocHJlZGljdGVkLmlkKSwgIm5vbmUiLCBwcmVkaWN0ZWQuaWQpKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKHg9bWV0aG9kLCBzdHJhdHVtPXByZWRpY3RlZC5pZCwgYWxsdXZpdW09Y2VsbCwgZmlsbD1wcmVkaWN0ZWQuaWQsIGxhYmVsPXByZWRpY3RlZC5pZCkpICsgLS0+CjwhLS0gICBnZW9tX2Zsb3coKSArIC0tPgo8IS0tICAgZ2VvbV9zdHJhdHVtKGNvbG9yPU5BKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KHN0YXQ9InN0cmF0dW0iKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSAjIyMjIFdoaWNoIGNlbGxzIGFyZSBpbmNvbnNpc3RlbnRseSBzY29yZWQ/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OH0gLS0+CjwhLS0gbGlicmFyeShnZ2FsbHV2aWFsKSAtLT4KPCEtLSBwcmVkLmxhYmVscy5kZiAlPiUgLS0+CjwhLS0gICBzZWxlY3QobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGNlbGwpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShwcmVkaWN0ZWQuaWQ9aWZlbHNlKGlzLm5hKHByZWRpY3RlZC5pZCksICJub25lIiwgcHJlZGljdGVkLmlkKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyh4PW1ldGhvZCwgc3RyYXR1bT1wcmVkaWN0ZWQuaWQsIGFsbHV2aXVtPWNlbGwsIGZpbGw9cHJlZGljdGVkLmlkLCBsYWJlbD1wcmVkaWN0ZWQuaWQpKSArIC0tPgo8IS0tICAgZ2VvbV9mbG93KCkgKyAtLT4KPCEtLSAgIGdlb21fc3RyYXR1bSgpICsgLS0+CjwhLS0gICBnZW9tX3RleHQoc3RhdD0ic3RyYXR1bSIpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgLS0+CjwhLS0gYGBgIC0tPgoKIyMgQWNjZXNzaWJpbGl0eSBvZiBtYXJrZXJzClRha2luZyBtYXJrZXJzIGZyb20gRmlnLiBTMiBvZiBKUCdzIG1hbnVzY3JpcHQKYGBge3IsIGZpZy5oZWlnaHQ9MTMsIGZpZy53aWR0aD0xMCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KdGh5bXVzLm1hcmtlcnMgPC0gYygiUFRQUkMiLCAiQ0QzRyIsICJUWVJPQlAiLCJDRDE5IiwiSE9YQTkiLCdGWFlEMicsIlNIM1RDMSIsIkNDUjkiLCJDRDhBIiwgIkNEOEIiLCJQRENEMSIsICJDUlRBTSIsIkNENDBMRyIsIkNDUjYiLCJGT1hQMyIsIlNPWDEzIiwiWk5GNjgzIiwiS0xSRDEiLCJUTkZTRjExIiwiVlBSRUIxIiwiTVM0QTEiLCAiQ0xFQzlBIiwgIkNMRUMxMEEiLCAiTEFNUDMiLCAiSUwzUkEiLCAiRkNHUjNCIiwgIkMyIiwiVFBTQjIiLAogICAgICAgICAgICAgICAgICAgICdJVEdBMkInLCJHWVBBIiwgIkNESDUiLCAiUkdTNSIsIkNESDEiLCAiUERHRlJBIiwiQ1JBQlAxIikKIyBwYm1jLm1hcmtlcnMgPC0gYygiQ0Q3OUEiLCAiTVM0QTEiLCAiQ0Q4QSIsICJDRDhCIiwgIkxZWiIpCiMgdGh5bXVzLm1hcmtlcnMgPC0gbGlzdChGYj1jKCJQREdGUkEiLCAiQ09MRUMxMSIsICJGQk4xIiwgIlBJMTYiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIFZTTUM9YygiUERHRlJCIiwgJ0FDVEEyJywgIlJHUzUiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIEVuZG89YygiUEVDQU0xIiwgIkNESDUiLCJMWVZFMSIpLAojICAgICAgICAgICAgICAgICAgICAgICAgVEVDID0gYygiRVBDQU0iLCAiRk9YTjEiLCAiQ0NMMjUiLCAiQ0NMMTkiKQojICAgICAgICAgICAgICAgICAgICAgICAgKQp0aHltdXMubWFya2Vycy5kZiA8LSBpbWFwKHRoeW11cy5tYXJrZXJzLCB+IGRhdGEuZnJhbWUoZ2VuZT0ueCwgY2VsbC50eXBlLmNsYXNzPS55KSkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpCgptYXJrZXIuYWNjZXNzLmRmIDwtIGF0YWMuc2V1QGFzc2F5cyRBQ1RJVklUWUBkYXRhW2ludGVyc2VjdCh0aHltdXMubWFya2Vycywgcm93bmFtZXMoYXRhYy5zZXVAYXNzYXlzJEFDVElWSVRZKSksXSAlPiUKICBhcy5tYXRyaXgoKSAlPiUKICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSwgdmFsdWUubmFtZT0ibG9nLmNvdW50cyIpICU+JQogIGZ1bGxfam9pbihyb3duYW1lc190b19jb2x1bW4oYXRhYy5zZXVAbWV0YS5kYXRhWywgbGFiZWxfY29sc10sICJjZWxsIikpICU+JQogICMgZnVsbF9qb2luKHRoeW11cy5tYXJrZXJzLmRmKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1sYWJlbF9jb2xzLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAicHJlZGljdGVkLmlkIikgJT4lCiAgZHBseXI6Om11dGF0ZShtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsIi4rXyIpKSAlPiUKICBmaWx0ZXIobWV0aG9kICVpbiUgYygiQ0NBIiwgIkxpZ2VyIiwgIkNvbm9zIikpIAoKb3JkZXJlZF9jZWxsX3R5cGVzIDwtIGMoIkROIiwgIkRQIChRKSIsICJEUCAoUCkiLCAiU1AgKDEpIiwgIk5LIiwgIklMQzMiLCAiREMiLCAiTWFjIiwgIkVyeSIsICJGaWIiKQoKbWFya2Vyc19wbCA8LSAKICBtYXJrZXIuYWNjZXNzLmRmICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQgPSBjYXNlX3doZW4oc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDgiKSB+ICJDRDgrVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHN0cl9kZXRlY3QocHJlZGljdGVkLmlkLCAiQ0Q0IikgfiAiQ0Q0K1QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHByZWRpY3RlZC5pZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICApICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQ9ZmFjdG9yKHByZWRpY3RlZC5pZCwgbGV2ZWxzID0gb3JkZXJlZF9jZWxsX3R5cGVzKSkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGdlbmUpICU+JQogIGRwbHlyOjptdXRhdGUoZnJhYy5jZWxscz1zdW0obG9nLmNvdW50cyA+IDApL24oKSkgJT4lCiAgIyBmaWx0ZXIobWV0aG9kPT0iQ0NBIikgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdCggYWVzKCBnZW5lLCBwcmVkaWN0ZWQuaWQpKSArCiAgZ2VvbV9wb2ludChhZXMoc2l6ZT1mcmFjLmNlbGxzLCBjb2xvcj1mcmFjLmNlbGxzKSkgKwogIGZhY2V0X2dyaWQobWV0aG9kfi4sIHNwYWNlPSJmcmVlIiwgc2NhbGVzPSJmcmVlX3giKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoaGlnaD0iZGFya2JsdWUiLCBsb3c9IndoaXRlIikgKwogICMgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEsIHZqdXN0PTAuNSksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1KSkgCgptYXJrZXJzX3BsIAogIApnZ3NhdmUocGFzdGUwKG91dGRpciwgIlRoeW11c19tYXJrZXJzX2FjY2Vzc2liaWxpdHkucG5nIiksIGhlaWdodCA9IDE2LCB3aWR0aCA9IDEyKQpgYGAKClJlcHJvZHVjaW5nIEZpZy4ySCBvbiBULWNlbGwgZGV2ZWxvcG1lbnQKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMn0KdC5jZWxsLm1hcmtlcnMgPC0gbGlzdChrbm93bi5tYXJrZXJzID0gYygiQ0QzNCIsICJJR0xMMSIsICJUUkdDMiIsICJUUkRDIiwgIlBUQ1JBIiwgIlRSQkMyIiwgIlRSQUMiLCAiQ0Q0IiwgIkNEOEEiLCAiQ0Q4QiIpLAogICAgICAgICAgICAgICAgICAgICAgIGNoZW1va2luZS5yZWNlcHRvcnMgPSBjKCJDQ1I5IiwgIkNDUjciKSwKICAgICAgICAgICAgICAgICAgICAgICB0Y3IuYWN0aXZhdGlvbiA9IGMoIkNENSIsICJDRDI3IiksCiAgICAgICAgICAgICAgICAgICAgICAgcHJvbGlmZXJhdGlvbj1jKCJQQ05BIiwgIkNESzEiLCAiTUtJNjciKSwKICAgICAgICAgICAgICAgICAgICAgICBjeWNsaW4uRCA9IGMoIkNDTkQyIiwgIkNDTkQzIiksCiAgICAgICAgICAgICAgICAgICAgICAgcmVjb21iaW5hdGlvbj1jKCJSQUcxIiwgIlJBRzIiKSwKICAgICAgICAgICAgICAgICAgICAgICBhcG9wdG9zaXM9YygiSFJLIiwiQk1GIiwgIlRQNTNJTlAxIiksCiAgICAgICAgICAgICAgICAgICAgICAgc3RhZ2UubWFya2VycyA9IGMoIlNUMTgiLCAiSElWRVAzIiwgIlJHUEQzIiwgIlNNUEQzIiwgIkFRUDMiLCAiUk9SQyIsICJTQVRCMSIsICJUT1gyIikKICAgICAgICAgICAgICAgICAgICAgICApIAp0LmNlbGwubWFya2Vycy5kZiA8LSBpbWFwKHQuY2VsbC5tYXJrZXJzLCB+IGRhdGEuZnJhbWUoZ2VuZT0ueCwgY2VsbC50eXBlLmNsYXNzPS55KSkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpCgpvcmRlcmVkLnRjZWxscyA8LSBjKCJETiIsICJEUCAoUCkiLCAiRFAgKFEpIiwiU1AgKDEpIikKCnRjZWxscy5tYXJrZXJzLmRmIDwtIAogIGF0YWMuc2V1QGFzc2F5cyRBQ1RJVklUWUBkYXRhW2ludGVyc2VjdCh1bmxpc3QodC5jZWxsLm1hcmtlcnMpLCByb3duYW1lcyhhdGFjLnNldUBhc3NheXMkQUNUSVZJVFkpKSxdICU+JQogIGFzLm1hdHJpeCgpICU+JQogIHJlc2hhcGUyOjptZWx0KHZhcm5hbWVzPWMoImdlbmUiLCAiY2VsbCIpLCB2YWx1ZS5uYW1lPSJsb2cuY291bnRzIikgJT4lCiAgZnVsbF9qb2luKHJvd25hbWVzX3RvX2NvbHVtbihhdGFjLnNldUBtZXRhLmRhdGFbLCBsYWJlbF9jb2xzXSwgImNlbGwiKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9bGFiZWxfY29scywgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gInByZWRpY3RlZC5pZCIpICU+JQogIGRwbHlyOjptdXRhdGUobWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCIuK18iKSkgJT4lCiAgZmlsdGVyKG1ldGhvZCAlaW4lIGMoIkNDQSIsICJMaWdlciIsICJDb25vcyIpKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWlmZWxzZShzdHJfZGV0ZWN0KHByZWRpY3RlZC5pZCwgIkNEOCsiKSwgIkNEOCtUIiwgcHJlZGljdGVkLmlkKSkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2Uoc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDQrIiksICJDRDQrVCIsIHByZWRpY3RlZC5pZCkpICU+JQogIGZpbHRlcihwcmVkaWN0ZWQuaWQgJWluJSBvcmRlcmVkLnRjZWxscykgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGdlbmUpICU+JQogIGRwbHlyOjptdXRhdGUoZnJhYy5jZWxscz1zdW0obG9nLmNvdW50cyA+IDApL24oKSwgbWVhbi5hY2M9bWVhbihsb2cuY291bnRzKSkgJT4lCiAgdW5ncm91cCgpIAoKdGNlbGxzLm1hcmtlcnMuZGYgJT4lCiAgZnVsbF9qb2luKHQuY2VsbC5tYXJrZXJzLmRmKSAlPiUKICAjIGZpbHRlcihtZXRob2Q9PSJDQ0EiKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWZhY3RvcihwcmVkaWN0ZWQuaWQsIGxldmVscz1vcmRlcmVkLnRjZWxscykpICU+JQogIGdncGxvdChhZXMoIHByZWRpY3RlZC5pZCwgZ2VuZSkpICsKICBmYWNldF9ncmlkKGNlbGwudHlwZS5jbGFzc35tZXRob2QsIHNjYWxlcyA9ICJmcmVlX3kiLCBzcGFjZT0iZnJlZSIpICsKICBnZW9tX3BvaW50KGFlcyhzaXplPWZyYWMuY2VsbHMsIGNvbG9yPW1lYW4uYWNjKSkgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGhpZ2g9ImRhcmtibHVlIiwgbG93PSJ3aGl0ZSIpICsKICAjIHNjYWxlX2NvbG9yX2dyYWRpZW50MihtaWRwb2ludCA9IDAuNSkgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEsIHZqdXN0PTAuNSksCiAgICAgICAgc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTApKSAKCmdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidGNlbGxfbWFya2Vycy5wbmciKSwgaGVpZ2h0ID0gMTQsIHdpZHRoID0gMTQpCgpgYGAKCjwhLS0gIyMjIENvbXBhcmUgZmVhdHVyZSBzZWxlY3Rpb24gc3RyYXRlZ3kgKHJlZmVyZW5jZSBiYXNlZCkgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHNldS5jY2EucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJDQ0FfcmVmZXJlbmNlX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKSAtLT4KPCEtLSBzZXUubGlnZXIucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJMaWdlcl9yZWZlcmVuY2VfaHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLlJEUyIpIC0tPgo8IS0tIHNldS5jb25vcy5yZWYgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3JlZmVyZW5jZV9odmdfRjc0X1NDRWxpc3RfMjAxOTExMDEuUkRTIikgLS0+Cgo8IS0tIGludGVncmF0ZV9mZWF0dXJlc19yZWYgPC0gc2Nhbigifi9tb2RlbHMvaW50RmVhdHVyZXNfcmVmZXJlbmNlX2h2Z18yMDAwX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLnR4dCIsIHdoYXQgPSAiIikgLS0+Cgo8IS0tIGludC5saXN0LnJlZiA8LSBsaXN0KENDQT1zZXUuY2NhLnJlZiwgTGlnZXI9c2V1LmxpZ2VyLnJlZiwgQ29ub3M9c2V1LmNvbm9zLnJlZikgLS0+Cgo8IS0tICMjIEFkZCB0byBhdGFjIFNldXJhdCBvYmplY3QgLS0+CjwhLS0gcHJlZC5jY2EucmVmIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY2NhLnJlZiwgIkNDQV9yZWYiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKSAtLT4KPCEtLSBwcmVkLmxpZ2VyLnJlZiA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmxpZ2VyLnJlZiwgIkxpZ2VyX3JlZiIpIC0tPgo8IS0tIHByZWQuY29ub3MucmVmIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY29ub3MucmVmLCAiQ29ub3NfcmVmIikgLS0+Cgo8IS0tIGlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsgLS0+CjwhLS0gICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYS5yZWYsIHByZWQubGlnZXIucmVmLCBwcmVkLmNvbm9zLnJlZikpIC0tPgo8IS0tIH0gZWxzZSB7IC0tPgo8IS0tICAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy53aWR0aD0xOSwgZmlnLmhlaWdodD05fSAtLT4KPCEtLSBnZ3B1YnI6OmdnYXJyYW5nZSggLS0+CjwhLS0gICBwbG90bGlzdCA9IGxpc3QoIC0tPgo8IS0tICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQV9yZWYiICAsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwgLS0+CjwhLS0gICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfTGlnZXJfcmVmIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksIC0tPgo8IS0tICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0Nvbm9zX3JlZiIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDb25vcyIpIC0tPgo8IS0tICAgKSwgLS0+CjwhLS0gICBjb21tb24ubGVnZW5kID0gVFJVRSwgbmNvbD0zLCBucm93PTEgLS0+CjwhLS0gKSAgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTE2fSAtLT4KPCEtLSBwcmVkLmxhYmVscy5yZWYuZGYgPC0gaW1hcChsaXN0KENDQT1wcmVkLmNjYS5yZWYsIExpZ2VyPXByZWQubGlnZXIucmVmLCBDb25vcz1wcmVkLmNvbm9zLnJlZiksIH4gIC0tPgo8IS0tICAgICAgIHJvd25hbWVzX3RvX2NvbHVtbigueCwgImNlbGwiKSAlPiUgLS0+CjwhLS0gICAgICAgcmVuYW1lX2FsbChmdW5zKHN0cl9yZW1vdmUoLiwgc3RyX2MoIl8iLC55KSkpKSAlPiUgLS0+CjwhLS0gICAgICAgbXV0YXRlKG1ldGhvZD0ueSkgLS0+CjwhLS0gICAgICkgJT4lIC0tPgo8IS0tICAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShzY29yZT1pZmVsc2UoaXMubmEoc2NvcmVfcmVmKSwgMCwgc2NvcmVfcmVmKSkgLS0+Cgo8IS0tIGZ1bGxfam9pbiggLS0+CjwhLS0gICBwcmVkLmxhYmVscy5kZiwgLS0+CjwhLS0gICBzZWxlY3QocHJlZC5sYWJlbHMucmVmLmRmLCBjZWxsLCBwcmVkaWN0ZWQuaWRfcmVmLCBzY29yZV9yZWYsIG1ldGhvZCksIC0tPgo8IS0tICAgYnk9YygiY2VsbCIsICJtZXRob2QiKSAtLT4KPCEtLSAgICkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShuX3ByZWQ9bigpKSAlPiUgLS0+CjwhLS0gICB1bmdyb3VwKCkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIHByZWRpY3RlZC5pZF9yZWYpICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZShuPW4oKSwgbl9wcmVkPW1heChuX3ByZWQpKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoZnJhYz1uL25fcHJlZCkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhwcmVkaWN0ZWQuaWQsIHByZWRpY3RlZC5pZF9yZWYpKSArIC0tPgo8IS0tICAgZ2VvbV90aWxlKGFlcyhmaWxsPWZyYWMpKSArIC0tPgo8IS0tICAgZmFjZXRfd3JhcChtZXRob2R+LiwgbnJvdz0xLCBuY29sPTMpICsgLS0+CjwhLS0gICBjb29yZF9maXhlZCgpICsgLS0+CjwhLS0gICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0id2hpdGUiLCBoaWdoPSJyZWQiKSArIC0tPgo8IS0tICAgeWxhYigiRmVhdC4gc2VsZWN0aW9uOiByZWZlcmVuY2UgSFZHIikgKyB4bGFiKCJGZWF0LiBzZWxlY3Rpb246IHVuaW9uIEhWRyIpICsgLS0+CjwhLS0gICB0aGVtZV9jb3dwbG90KGZvbnRfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKSArIC0tPgo8IS0tICAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bmlvblZTcmVmZXJlbmNlLnBuZyIpLCBoZWlnaHQgPSAxMiwgd2lkdGg9MTApIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+Cgo8IS0tIHNjb3JlLkNDQS5yZWYgPC0gICBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY2NhLnJlZlsueCwxXSA9PSBwcmVkLmNjYS5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5Db25vcy5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNvbm9zLnJlZlsueCwxXSA9PSBwcmVkLmNvbm9zLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkxpZ2VyLnJlZiA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQubGlnZXIucmVmWy54LDFdID09IHByZWQubGlnZXIucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+Cgo8IS0tIGtubl9zY29yZV9yZWZfZGYgPC0gLS0+CjwhLS0gICBhcy5kYXRhLmZyYW1lKGNiaW5kKHNjb3JlLkNvbm9zLnJlZiwgc2NvcmUuTGlnZXIucmVmLCBzY29yZS5DQ0EucmVmKSkgJT4lIC0tPgo8IS0tICAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lIC0tPgo8IS0tICAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RyX3N1YnNldChjb2xuYW1lcyguKSwgInNjb3JlIiksIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJLTk5fc2NvcmUiKSAlPiUgLS0+CjwhLS0gICBkcGx5cjo6bXV0YXRlKEtOTl9zY29yZT1pZmVsc2UoaXMubmEoS05OX3Njb3JlKSwgMCwgS05OX3Njb3JlKSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgIG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgInNjb3JlLiIpKSAtLT4KCjwhLS0gcXVhbnRzID0gc2VxKDAsMSwgYnkgPSAwLjA1KSAtLT4KPCEtLSBBVUVDREZfa25uX3Njb3JlIDwtIGtubl9zY29yZV9yZWZfZGYgJT4lIC0tPgo8IS0tICAgc3BsaXQoLiRtZXRob2QpICU+JSAtLT4KPCEtLSAgIG1hcF9kYmwoIH4gLnggJT4lIC0tPgo8IS0tICAgICAgIGFycmFuZ2UoS05OX3Njb3JlKSAlPiUgIC0tPgo8IS0tICAgICAgIHtlY2RmKC4kS05OX3Njb3JlKShxdWFudHMpfSAlPiUgQVVDKHF1YW50cywuKSAtLT4KPCEtLSAgICAgKSAtLT4KCjwhLS0ga25uX3Njb3JlX3JlZl9kZiAlPiUgLS0+CjwhLS0gICBtdXRhdGUoQVVDPUFVRUNERl9rbm5fc2NvcmVbbWV0aG9kXSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPW1ldGhvZCwgZmlsbD1tZXRob2QpKSArIC0tPgo8IS0tICAgc3RhdF9lY2RmKHNpemU9MSkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KGRhdGE9LiAlPiUgZ3JvdXBfYnkobWV0aG9kKSAlPiUgc3VtbWFyaXNlKEFVQz1tYXgoQVVDKSksICAtLT4KPCEtLSAgICAgICAgICAgICB4PTAuMDUsIGhqdXN0PTAsIC0tPgo8IS0tICAgICAgICAgICAgIGFlcyhsYWJlbD1nbHVlKCJBVUMgPSB7cm91bmQoQVVDLCAzKX0iKSwgeT1jKDAuOTAsIDAuOTUsIDEpKSkgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgeWxhYigiRUNERiIpICAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tICMjIyBJcyB0aGUgdW5pb24gb3IgdGhlIHJlZmVyZW5jZSBiZXN0IG1haW50YWluaW5nIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIEFUQUM/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTUsZmlnLmhlaWdodD03fSAtLT4KPCEtLSBrID0gNTAgLS0+CjwhLS0gYXRhYy5zZXUgPC0gRmluZE5laWdoYm9ycyhhdGFjLnNldSwgYXNzYXkgPSAiQVRBQyIsIHJlZHVjdGlvbiA9ICJTbmFwQVRBQyIsIGRpbXMgPSAxOjE1LCBrLnBhcmFtID0gaykgLS0+Cgo8IS0tIGF0YWMubm4ubGlzdCA8LSBnZXROTmxpc3QoYXRhYy5zZXUpIC0tPgoKPCEtLSBzY29yZS5DQ0EgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYVsueCwxXSA9PSBwcmVkLmNjYVsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkNvbm9zIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jb25vc1sueCwxXSA9PSBwcmVkLmNvbm9zWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuTGlnZXIgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmxpZ2VyWy54LDFdID09IHByZWQubGlnZXJbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KCjwhLS0ga25uX3Njb3JlX2RmIDwtIC0tPgo8IS0tICAgYXMuZGF0YS5mcmFtZShjYmluZChzY29yZS5Db25vcywgc2NvcmUuTGlnZXIsIHNjb3JlLkNDQSkpICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JSAtLT4KPCEtLSAgIHBpdm90X2xvbmdlcihjb2xzPXN0cl9zdWJzZXQoY29sbmFtZXMoLiksICJzY29yZSIpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiS05OX3Njb3JlIikgJT4lIC0tPgo8IS0tICAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSksIC0tPgo8IS0tICAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkgLS0+CgoKPCEtLSBzY29yZS5DQ0EucmVmIDwtICAgaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYS5yZWZbLngsMV0gPT0gcHJlZC5jY2EucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuQ29ub3MucmVmIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jb25vcy5yZWZbLngsMV0gPT0gcHJlZC5jb25vcy5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5MaWdlci5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmxpZ2VyLnJlZlsueCwxXSA9PSBwcmVkLmxpZ2VyLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgoKPCEtLSBrbm5fc2NvcmVfcmVmX2RmIDwtIC0tPgo8IS0tICAgYXMuZGF0YS5mcmFtZShjYmluZChzY29yZS5Db25vcy5yZWYsIHNjb3JlLkxpZ2VyLnJlZiwgc2NvcmUuQ0NBLnJlZikpICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JSAtLT4KPCEtLSAgIHBpdm90X2xvbmdlcihjb2xzPXN0cl9zdWJzZXQoY29sbmFtZXMoLiksICJzY29yZSIpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiS05OX3Njb3JlIikgJT4lIC0tPgo8IS0tICAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSksIC0tPgo8IS0tICAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkgLS0+CgoKPCEtLSBiaW5kX3Jvd3Moa25uX3Njb3JlX2RmLCBrbm5fc2NvcmVfcmVmX2RmKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoZmVhdHVyZS5zZWxlY3Rpb249aWZlbHNlKHN0cl9kZXRlY3QobWV0aG9kLCAicmVmIiksICJyZWYiLCAidW5pb24iKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgIi5yZWYiKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPWZlYXR1cmUuc2VsZWN0aW9uLCBmaWxsPW1ldGhvZCkpICsgLS0+CjwhLS0gICBzdGF0X2VjZGYoc2l6ZT0xKSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgLS0+CjwhLS0gICBmYWNldF93cmFwKG1ldGhvZH4uKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsgLS0+CjwhLS0gICB5bGFiKCJFQ0RGIikgKyAtLT4KPCEtLSAgIGdndGl0bGUocGFzdGUoIksgPSIsIGspKSArIC0tPgo8IS0tICAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bmlvblZTcmVmZXJlbmNlX0tOTi5wbmciKSwgaGVpZ2h0ID0gNCwgd2lkdGggPSAxMCkgLS0+Cgo8IS0tIGBgYCAtLT4KCjwhLS0gLS0tIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdGx5OjpnZ3Bsb3RseShEaW1QbG90KG9yaWcuUk5BLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiKSkgLS0+CjwhLS0gcGxvdGx5OjpnZ3Bsb3RseShEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQSIpKSAtLT4KPCEtLSBgYGAgLS0+CgoKIyMjIFRob3VnaHRzCi0gQ29ub3Mgc2NvcmVzIGEgbG90IG9mIGNlbGxzIHdpdGggaGlnaCBjb25maWRlbmNlLCBidXQgZmFpbHMgdG8gYXNzaWduIGNlbGxzIHRvIGRpZmZpY3VsdCBjbHVzdGVycyAKLSBDQ0EgcmVzZW1ibGVzIHRoZSBjb21wb3NpdGlvbiBvZiB0aGUgUk5BIGRhdGEgYmV0dGVyLCBidXQgY3VyaW91cyB0aGF0IHRoZSBvdGhlciBtZXRob2RzIGlkZW50aWZ5IHdheSBtb3JlIAoKCgoKCgoKCgoK